﻿/*********************************************************************
 *
 *                             NOTICE
 *               COPYRIGHT (c) 2012 Panoscopix Software, Inc.
 *                UNPUBLISHED - ALL RIGHTS RESERVED
 * 
 *      This program is confidential, proprietary, and a trade
 *   secret of Panoscopix Software, Inc. The receipt or possession of
 *     this program does not convey any rights to reproduce or
 *      disclose its contents, or to manufacture, use, or sell
 *       anything that it may describe, in whole or in part, 
 *   without the specific written consent of Panoscopix Software, Inc.
 *
 * A set of utility methods for working with files.
 * 
 *******************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FileSystem.Utility
{
    class FileUtil
    {
        /// <summary>
        /// Returns node with supplied local name, or null if there is no node with that name.
        /// </summary>
        public static IFSNode findNodeWithLocalName(IFSDirectory dir, String name) {
		    if (StringUtil.isNullOrEmpty(name)) return null;
		    return scanNodeForLocalName(dir, name);
	    }

        /// <summary>
        /// Returns node with supplied pathname (using root if necessary to process absolute paths), or null
        /// if there is no node with that name.  PathNameFormatException thrown only for parse errors
        /// of the pathname; null will be returned if there is no parse error, but the node does not exist.
        /// </summary>
        public static IFSNode findNodeWithPathName(IFSDirectory root, IFSDirectory dir, String path) //throws PathNameParseException
	    {
		    PathFragmentTO fragment = determinePathFragment(root, dir, path);
		    if (null == fragment.remainingPath) {
			    return fragment.lastExistingNode;
		    }
		    return null;
	    }

        /// <summary>
        /// Returns processing of path up to the last node that does exist.  Never returns null.  If the full
        /// path exists, then PathFragmentVO.remainingPath is null.  Exception thrown only for parse errors
        /// on the pathname up to the last node that exists (there may be errors in remainingPath that were
        /// not detected).
        /// </summary>
        public static PathFragmentTO determinePathFragment(IFSDirectory root, IFSDirectory dir, String path) //throws PathNameParseException
	    {
		    if (StringUtil.isNullOrEmpty(path)) {
			    return new PathFragmentTO(dir, null);
		    }
		
		    IFSDirectory target = dir;
		    if (StringUtil.startsWith(path, "/")) {
			    target = root;
			    path = StringUtil.substring(path, 1);
		    }
		
		    return processPath(target, path);
	    }

        /// <summary>
        /// Returns (via output parameter nodes) the nodes that are created to achieve the supplied path.
        /// The intervening directories and the last node are created according to the specified builder.
        /// </summary>
        /// <param name="filesys">The filesystem to use for node creation.</param>
        /// <param name="current">The current node to start building the tree.</param>
        /// <param name="path">The path to build.</param>
        /// <param name="builder">Factory for the intervening directories and last node.</param>
        /// <param name="nodes">The nodes built.</param>
        /// <returns>Return value indicates success or failure in the tree.  If failed, no nodes are created.</returns>
        public static bool addTree(IFSFileSystem filesys, IFSNode current, String path,
			TreeBuilderHelper builder, IList<IFSNode> nodes)
	    {
		    String nodeName = StringUtil.firstToken(path, '/');
		    INodeMetaDataVO metaData = filesys.getMetaDataForEmptyNode(nodeName);

		    String nextName = StringUtil.remainder(path, '/');
		    if (StringUtil.isNullOrEmpty(nextName)) {
			    //nodeName is the last node, so make it accordingly and done
			    IFSNode last = builder.createLastNodeInParent(current, metaData);
			    if (null != last) {
                    nodes.Add(last);
				    return true;
			    }
			    return false;
		    }
		
		    //nextName is not empty, so this node will be a directory.
		    IFSDirectory dir = builder.createDirectoryInParent(current, metaData);
		    if (null == dir) return false; //revert any predecessors in addTree
		
		    //this node succeeded, so proceed to the next node in the remaining path.
		    nodes.Add(dir);
		    bool rc = addTree(filesys, dir, nextName, builder, nodes);  //dir created above is now context for next path
		    if (rc) return true;  //subtree succeeded, we succeeded, so return success.
		
		    //subtree failed, so we need to revert and indicate to revert any predecessors in the addTree
		    current.removeNode(nodeName);
		    nodes.Remove(dir); //our node should be the node at the end of the vector.
		    return false;
	    }

        private static IFSNode scanNodeForLocalName(IFSNode dir, String name) {
		    for (int i = 0; i < dir.numNodes(); i++) {
			    IFSNode node = dir.getNode(i);
			    if (StringUtil.equals(name, node.getName())) {
				    return node;
			    }
		    }
		    return null;
	    }
	
        private static PathFragmentTO processPath(IFSNode target, String path) //throws PathNameParseException
	    {
		    if (StringUtil.startsWith(path, "../")) {
			    IFSDirectory parent = target.getParent();
			    if (null == parent) throw new PathNameParseException("Root does not have parent.");
			    return processPath(parent, StringUtil.substring(path, 3));
		    } else if (StringUtil.equals(path, "..")) {
			    IFSDirectory parent = target.getParent();
			    if (null == parent) throw new PathNameParseException("Root does not have parent.");
			    return new PathFragmentTO(parent, null);
		    } else if (StringUtil.startsWith(path, "./")) {
			    //self
			    return processPath(target, StringUtil.substring(path, 2));
		    } else if (StringUtil.equals(path, ".")) {
			    //self
			    return new PathFragmentTO(target, null);
		    } else if (StringUtil.equals(path, "/")) {
			    //self...this is a trailing "/", not a leading "/".
			    return new PathFragmentTO(target, null);
		    } else if (StringUtil.isEmpty(path)) {
			    //self
			    return new PathFragmentTO(target, null);
		    }

		    //path must start with a name
		    String name = StringUtil.firstToken(path, '/');
		    if (StringUtil.isNullOrEmpty(name)) throw new PathNameParseException("Unexpected empty name in path '" + path + "'.");

		    IFSNode child = scanNodeForLocalName(target, name);
		    if (null == child) {
			    return new PathFragmentTO(target, path);
		    }
		    return processPath(child, StringUtil.remainder(path, '/'));
	    }
    }
}
